package com.kong.meirimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.kong.meirimall.product.service.CategoryBrandRelationService;
import com.kong.meirimall.product.vo.Catalog2Vo;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kong.common.utils.PageUtils;
import com.kong.common.utils.Query;

import com.kong.meirimall.product.dao.CategoryDao;
import com.kong.meirimall.product.entity.CategoryEntity;
import com.kong.meirimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

//    @Autowired
//    CategoryDao categoryDao;
    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redisson;

    @Override
    public List<CategoryEntity> listWithTree() {
        //1.查出所有分类
        // 直接用 baseMapper，因为 ServiceImpl 中 将 CategoryDao extends 成了 该泛型
        List<CategoryEntity> entities = baseMapper.selectList(null);// 没有查询条件（即查所有）
        //2.组装成父子的树形结构
        // 找到所有的一级分类（父id 为 0 的）
        List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map((menu)->{
                    menu.setChildren(getChildren(menu,entities));
                    return menu;
                }).sorted((menu1,menu2)->((menu1.getSort()==null ? 0:menu1.getSort())-(menu2.getSort()==null ? 0:menu2.getSort())
                )).collect((Collectors.toList()));
        return level1Menus;
    }
    // 递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
            categoryEntity.setChildren(getChildren(categoryEntity,all));
            return categoryEntity;
                }).sorted((menu1,menu2)->{
                    return ((menu1.getSort()==null?0: menu1.getSort())-(menu2.getSort()==null?0: menu2.getSort()));
                }).collect(Collectors.toList());
        return children;
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public void removeMenueByIds(List<Long> list) {
        // 代办事项列表，可在左下角查看
        // TODO 1.检查当前删除的菜单，是否被别的地方引用

        // 逻辑删除，查看 mybatis plus 的官方文档，表中要有一个标志逻辑删除状态的字段
        // 1). 配置全局的逻辑删除规则(yml中)（可省略）
        // 2). 配置逻辑删除的组件 Bean （mybatis plus 3.1 以上版本不需要）
        // 3). 给 showStatus 属性加上逻辑删除注解 @Tablelogic
        baseMapper.deleteBatchIds(list);
    }
    //[2,25,225]
    @Override
    public Long[] findCatelogPath(Long catalogId) {
        List<Long> paths = new ArrayList<>();
        findParentPath(catalogId, paths);
        Collections.reverse(paths);  //存入的顺序是 子->父，而路径是 父->子，需要将集合逆序
        return  paths.toArray(new Long[paths.size()]);
    }

    // 级联更新所有关联的数据（当前表和其他表）
    @Caching(evict={
            @CacheEvict(value = "category",key = "'getLevel1Categorys'"),
            @CacheEvict(value = "category",key = "'geCatelogJson'")
    })
    @Transactional     //事务
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }

    private void findParentPath(Long catalogId,List<Long> Paths) {
        Paths.add(catalogId);
        CategoryEntity category = this.getById(catalogId);
        //向上查找父，一层层递归
        if (category.getParentCid() != 0) {
            findParentPath(category.getParentCid(), Paths);
        }
    }

    @Cacheable(value = "category",key = "#root.method.name")           // 代表当前方法的结果需要缓存，如果缓存中有，方法不用调用
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("Get Level 1 Categorys");
        List<CategoryEntity> selectList = baseMapper.selectList(null);
        // 1、查出所有一级分类
        List<CategoryEntity> level1Categorys = getParentCid(selectList,0L);
        return level1Categorys;
    }

    @Cacheable(value = "category",key = "#root.method.name")
    @Override
    public Map<String, List<Catalog2Vo>> geCatelogJson() {
        /**
         * 1、空结果缓存：解决穿透
         * 2、设置过期时间（加随机值）：解决雪崩
         * 3、加锁：解决击穿
         */
        Map<String, List<Catalog2Vo>> stringListMap=null;
        //缓存的数据 都是 json 字符串形式（全平台兼容）
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String catelogJson = ops.get("catalogJson");
        if(StringUtils.isEmpty(catelogJson)){
            stringListMap = geCatelogJsonFromDb();
        }else {
            stringListMap = JSON.parseObject(catelogJson,new TypeReference<Map<String, List<Catalog2Vo>>>() {});
        }
        return stringListMap;
    }

    private List<CategoryEntity> getParentCid(List<CategoryEntity> selectList,Long parentCid) {
        List<CategoryEntity> collect = selectList.stream().filter(item -> item.getParentCid().equals(parentCid)).collect(Collectors.toList());
        return collect;
    }
    // 从数据库查询 三级分类 并存入缓存
    @Override
    public Map<String, List<Catalog2Vo>> geCatelogJsonFromDb(){
            ValueOperations<String, String> ops = redisTemplate.opsForValue();
            String catelogJson = ops.get("catalogJson");
            if(!StringUtils.isEmpty(catelogJson)) {
                Map<String, List<Catalog2Vo>> stringListMap = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
                });
                return stringListMap;
            }
            System.out.println("查询了数据库");
            // 加索后，线程需排队执行，在缓存中判断，如果没有再继续查询
            // 将数据库的多次查询变为 1 次，提高性能
            List<CategoryEntity> selectList = baseMapper.selectList(null);
            // 1、查出所有一级分类
            List<CategoryEntity> level1Categorys = getParentCid(selectList,0L);
            // 2、封装子对象
            Map<String, List<Catalog2Vo>> map = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                // 查到每个一级分类的所有二级分类,转化，再转为 list 集合
                List<CategoryEntity> categoryEntities = getParentCid(selectList,v.getCatId());
                List<Catalog2Vo> catalog2Vos = null;
                if (categoryEntities != null) {
                    catalog2Vos = categoryEntities.stream().map(l2 -> {
                        Catalog2Vo catalog2Vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                        List<CategoryEntity> level3Catalogs = getParentCid(selectList,l2.getCatId());
                        List<Catalog2Vo.Catalog3Vo> catalog3Vos=null;
                        if(level3Catalogs!=null){
                            catalog3Vos = level3Catalogs.stream().map(l3 -> {
                                Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                                return catalog3Vo;
                            }).collect(Collectors.toList());
                            catalog2Vo.setCatalog3List(catalog3Vos);
                        }
                        return catalog2Vo;
                    }).collect(Collectors.toList());
                }
                return catalog2Vos;
            }));
            catelogJson = JSON.toJSONString(map);
            ops.set("catalogJson", catelogJson,1, TimeUnit.DAYS);
            return map;
    }

    /**
     * @return
     */
    @Override
    public Map<String, List<Catalog2Vo>> geCatelogJsonFromDbWithRedisLock(){
        // 1、抢占分布式锁，去 redis 占坑
        RLock lock = redisson.getLock("CatalogJson-lick");
        lock.lock();
        Map<String, List<Catalog2Vo>> dataFromDb;
        try{
            dataFromDb = geCatelogJsonFromDb();
        }finally {
            lock.unlock();
        }
        return dataFromDb;
    }
}
